<!DOCTYPE html>


<html lang="zh-CN">


<head>
  <meta charset="utf-8" />
    
  <meta name="description" content="welcome" />
  
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
    Java多线程内存模型(JMM) |  ChenyyのBlog
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="/favicon.ico" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">
  
<link rel="stylesheet" href="/css/custom.css">

  
  <script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>
  
  

  

</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-Java/Java多线程内存模型(JMM)"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  Java多线程内存模型(JMM)
</h1>
 

    </header>
     
    <div class="article-meta">
      <a href="/2021/06/01/Java/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B(JMM)/" class="article-date">
  <time datetime="2021-06-01T12:21:58.000Z" itemprop="datePublished">2021-06-01</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/Java/">Java</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">4.6k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">17 分钟</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <ul>
<li>CUP多核并发缓存架构</li>
<li>数据的可见性，有序性和原子性</li>
<li>JMM多线程内存模型</li>
<li>volatile关键字</li>
</ul>
<span id="more"></span>

<h1 id="CUP多核并发缓存架构"><a href="#CUP多核并发缓存架构" class="headerlink" title="CUP多核并发缓存架构"></a>CUP多核并发缓存架构</h1><p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210531215231.png"></p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210531220224.png"></p>
<p>CPU缓存一致性协议MESI</p>
<h2 id="CPU高速缓存-Cache-Memory"><a href="#CPU高速缓存-Cache-Memory" class="headerlink" title="CPU高速缓存(Cache Memory)"></a>CPU高速缓存(Cache Memory)</h2><p>CPU在摩尔定律的指导下以每18个月起一番的速度在发展，然而内存和硬盘的发展速度远远不及CPU。这就造成了高性能能的内存和硬盘价格及其昂贵。然而CPU的高度运算需要高速的数据。</p>
<p>CPU在摩尔定律的指导下以每18个月起一番的速度在发展，然而内存和硬盘的发展速度远远不及CPU。这就造成了高性能能的内存和硬盘价格及其昂贵。然而CPU的高度运算需要高速的数据。</p>
<p>为了解决这个问题，CPU厂商在CPU中内置了少量的高速毁存以解决T\O速度和CPU运算速度之间的不P有问题。</p>
<p>在CPU访问存储设备时，无论是存取数据抑或存取指令，都趋于聚集在一片连续的区域中，这就被称为<strong>局部性原理</strong>：</p>
<ul>
<li>时间局部性(Temporal Locality)：如果一个信息项正在被访问，那么在近期它很可能还会被再次访问。比加循环、递归、方法的反复调用等。</li>
<li>空间局部性(Spatlal Locality)：如果一个存储器的位置被引用。那么将来他附近的位置也会被引用。比如顺序执行的代码、连续创建的两个对象、数组等。</li>
</ul>
<p><strong>带有高速缓存的CPU执行计算的流程</strong>：</p>
<ul>
<li>程序以及数据被加载到主内存</li>
<li>指令和数据被加载到CPU的高速缓存</li>
<li>CPU执行指令，把结果写到高速缓存</li>
<li>高速缓存中的数据写回主内存</li>
</ul>
<h2 id="多核CPU多级缓存一致性协议MESI"><a href="#多核CPU多级缓存一致性协议MESI" class="headerlink" title="多核CPU多级缓存一致性协议MESI"></a>多核CPU多级缓存一致性协议MESI</h2><p>多核CPU的情况下有多个一级缓存，如何保证缓存内部数据的一致，不让系统数据混乱。这里就引出了一个一致性的协议MESI。</p>
<p>MESI是指4中状态的首字母。每个Cache line有4个状态，可用2个bit表示。</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210531224013.png"></p>
<h1 id="数据的可见性，有序性和原子性"><a href="#数据的可见性，有序性和原子性" class="headerlink" title="数据的可见性，有序性和原子性"></a>数据的可见性，有序性和原子性</h1><h2 id="原子性"><a href="#原子性" class="headerlink" title="原子性"></a>原子性</h2><h3 id="什么是原子性"><a href="#什么是原子性" class="headerlink" title="什么是原子性"></a>什么是原子性</h3><p>原子性指一系列的操作，要么全部执行成功，要么全部不执行，不会出现执行一半的情况，是不可分的。</p>
<h3 id="原子性怎么实现"><a href="#原子性怎么实现" class="headerlink" title="原子性怎么实现"></a>原子性怎么实现</h3><ul>
<li>使用synchronized或Lock加锁实现，保证任一时刻只有一个线程访问该代码块</li>
<li>使用原子操作</li>
</ul>
<h3 id="Java中的原子操作有哪些"><a href="#Java中的原子操作有哪些" class="headerlink" title="Java中的原子操作有哪些"></a>Java中的原子操作有哪些</h3><ul>
<li>除long和double之外的基本类型的赋值操作（64位值，当成两次32位的进行操作）</li>
<li>所有引用reference的赋值操作</li>
<li>java.concurrent.Atomic.*包中所有类的原子操作</li>
</ul>
<h3 id="创建对象的过程是否是原子操作"><a href="#创建对象的过程是否是原子操作" class="headerlink" title="创建对象的过程是否是原子操作"></a>创建对象的过程是否是原子操作</h3><p>创建对象实际上有3个步骤，并不是原子性的（常应用于双重检查+volatile创建单例场景）</p>
<ul>
<li>创建一个空对象</li>
<li>调用构造方法</li>
<li>创建好的实例赋值给引用</li>
</ul>
<h2 id="可见性"><a href="#可见性" class="headerlink" title="可见性"></a>可见性</h2><h3 id="什么是可见性问题"><a href="#什么是可见性问题" class="headerlink" title="什么是可见性问题"></a>什么是可见性问题</h3><p>可见性指的是当一个线程修改了某个共享变量的值，其他线程是否能够马上得知这个修改的值。</p>
<h3 id="为什么会有可见性问题"><a href="#为什么会有可见性问题" class="headerlink" title="为什么会有可见性问题"></a>为什么会有可见性问题</h3><p>对于多线程程序而言，线程将共享变量拷贝到各自的工作内存进行操作。线程A读取共享变量后，其他线程再对共享变量的修改，对于线程A来说并不可见，这就造成了可见性问题。</p>
<h3 id="如何解决可见性问题"><a href="#如何解决可见性问题" class="headerlink" title="如何解决可见性问题"></a>如何解决可见性问题</h3><ul>
<li>加volatile关键字保证可见性。共享变量被volatile修饰时，它会保证修改的值立即被其他的线程看到。</li>
<li>使用synchronized和Lock保证可见性。保证任一时刻只有一个线程能访问共享资源，并在其释放锁之前将修改的变量刷新到内存中。</li>
</ul>
<p>volatile,synchronized可见性，有序性，原子性代码证明：<a target="_blank" rel="noopener" href="https://blog.csdn.net/duyabc/article/details/111561857">https://blog.csdn.net/duyabc/article/details/111561857</a></p>
<h2 id="有序性（重排序）"><a href="#有序性（重排序）" class="headerlink" title="有序性（重排序）"></a>有序性（重排序）</h2><h3 id="什么是重排序"><a href="#什么是重排序" class="headerlink" title="什么是重排序"></a>什么是重排序</h3><p>在线程内部的两行代码的实际执行顺序和代码在Java文件中的逻辑顺序不一致，代码指令并不是严格按照代码语句顺序执行的，他们的顺序被改变了，这就是重排序。</p>
<h3 id="重排序的意义"><a href="#重排序的意义" class="headerlink" title="重排序的意义"></a>重排序的意义</h3><p>JVM能根据处理器特性（CPU多级缓存系统、多核处理器等）适当的对机器指令进行重排序，使机器指令能更符合CPU的执行特性，最大限度的发挥机器性能。</p>
<h3 id="重排序的3种情况"><a href="#重排序的3种情况" class="headerlink" title="重排序的3种情况"></a>重排序的3种情况</h3><ul>
<li>编译器优化（ JVM,JIT编辑器等）： 编译器在不改变单线程程序语义放入前提下，可以重新安排语句的执行顺序</li>
<li>指令级并行的重排序：现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性，处理器可以改变语句对应机器指令的执行顺序。</li>
<li>内存系统的重排序： 由于处理器使用缓存和读写缓冲区，这使得加载和存储操作看上去可能是在乱序执行。</li>
</ul>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210601164202.png"></p>
<h3 id="重排序遵循原则"><a href="#重排序遵循原则" class="headerlink" title="重排序遵循原则"></a>重排序遵循原则</h3><p>重排序会遵循as-if-serial与happens-before原则</p>
<p><strong>as-if-serial原则</strong></p>
<p>不管怎么重排序（编译器和处理器为了提高并行度)，(单线程）程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。</p>
<p>为了遵守as-f-serial语义，编译器和处理器不会对存在数据依赖关系的操作做重排序，因为这种重排序会改变执行结果。但是，如果操作之间不存在数据依赖关系，这些操作就可能被编译器和处理器重排序。</p>
<p><strong>happens-before原则</strong></p>
<p>从JDK5开始，提供了happens-before 原则来辅助保证程序执行的原子性、可见性以及有序性的问题，它是判断数据是否存在竞争、线程是否安全的依据，happens-before原则内容如下：</p>
<ul>
<li>1、程序顺序原则：即在一个线程内必须保证语义串行性，也就是说按照代码顺序执行。</li>
<li>2、锁规则：解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)之前，也就是说，如果对于一个锁解锁后，再加锁，那么加晚的动作必须在解锁动作之后(同一个锁)。</li>
<li>3、volatile规则：volatile变量的写，先发生于读，这保证了volatile变量的可见性，简单的理解就是，volatle变量在每次被线程访问时，都强迫从主内存中读该变量的值，而当该变量发生变化时，又会强迫将最新的值刷新到主内存，任何时刻，不同的线程总是能够看到该变量的最新值。</li>
<li>4、线程启动规则：线程的start()方法先于它的每一个动作，即如果线程A在执行线程B的start方法之前修改了共享变量的值，那么当线程B执行start方法时，线程A对共享变量的修改对线程B可见。</li>
<li>5、传递性：A先于B，B先于C那么A必然先于C。</li>
<li>6、线程终止规则：线程的所有操作先于线程的终结，Thread.join()方法的作用是等待当前执行的线程终止。假设在线程B终止之前，修改了共享变量，线程A从线程B的join方法成功返回后，线程B对共享变量的修改将对线程A可见。</li>
<li>7、线程中断规则：对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生，可以通过Thread.interrupted()方法检测线程是否中断。</li>
<li>8、对象终结规则：对象的构造函数执行，结束先于finalize()方法。</li>
</ul>
<h3 id="双重检测锁DCL-DoubleCheckLock-对象半初始化问题"><a href="#双重检测锁DCL-DoubleCheckLock-对象半初始化问题" class="headerlink" title="双重检测锁DCL(DoubleCheckLock)对象半初始化问题"></a>双重检测锁DCL(DoubleCheckLock)对象半初始化问题</h3><p>双重检查锁定DCL减少了锁粒度，不需要对整个getInstance()方法加synchronized锁，提高了方法被多个线程频繁的调用时的性能。</p>
<p>在对象创建好之后，执行getInstance()方法将不需要获取锁，检查instance不为空，就直接返回。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">public class Singleton &#123;</span><br><span class="line">    // volatile禁止指令重排，保证构造方法中的数据实例化</span><br><span class="line">    //private static volatile Singleton singleton;</span><br><span class="line">    private static Singleton singleton;</span><br><span class="line">    private Singleton() &#123;&#125;</span><br><span class="line"></span><br><span class="line">    public static Singleton getInstance() &#123;</span><br><span class="line">        if (singleton == null) &#123;</span><br><span class="line">            synchronized (Singleton.class) &#123;</span><br><span class="line">                if (singleton == null) &#123;</span><br><span class="line">                    singleton = new Singleton();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        return singleton;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上述代码由于Singleton没有加volatile，代码<code>instance = new Singleton();</code>创建一个对象时，这一行代码可以分解为如下的三行伪代码：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// instance = new Singleton();</span><br><span class="line">memory = allocate();   //1：分配对象的内存空间</span><br><span class="line">ctorInstance(memory);  //2：初始化对象</span><br><span class="line">instance = memory;     //3：设置instance指向刚分配的内存地址</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>而代码中的2和3之间，可能会被重排序。即instance先指向刚分配的内存地址，之后再进行对象的初始化。这可能会导致一个线程可能读到尚未初始化的Bean，而这个instance的确是!=null的。</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210605201751.png"></p>
<p>测试代码：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">@Data</span><br><span class="line">public class Singleton &#123;</span><br><span class="line"></span><br><span class="line">    private static Singleton instance;</span><br><span class="line">    private String name;</span><br><span class="line"></span><br><span class="line">    public static Singleton getInstance() throws InterruptedException &#123;</span><br><span class="line">        System.out.println(&quot;运行 &quot;+Thread.currentThread().getName());</span><br><span class="line">        if (instance == null) &#123;</span><br><span class="line">            synchronized (Singleton.class) &#123;</span><br><span class="line">                if (instance == null) &#123;</span><br><span class="line">                    instance = new Singleton();</span><br><span class="line">                    System.out.println(&quot;new Singleton()执行完毕&quot;);</span><br><span class="line">                    //耗时的初始化</span><br><span class="line">                    TimeUnit.SECONDS.sleep(3);</span><br><span class="line">                    instance.setName(&quot;chenchenchen&quot;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        return instance;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public static void main(String[] args) throws InterruptedException &#123;</span><br><span class="line">        Runnable runnable = new Runnable() &#123;</span><br><span class="line">            @Override</span><br><span class="line">            public void run() &#123;</span><br><span class="line">                try &#123;</span><br><span class="line">                    Singleton instance = Singleton.getInstance();</span><br><span class="line">                    System.out.println(&quot;get name :&quot;+instance.getName().toString());</span><br><span class="line">                &#125; catch (InterruptedException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line"></span><br><span class="line">        Thread thread1 = new Thread(runnable);</span><br><span class="line">        Thread thread2 = new Thread(runnable);</span><br><span class="line">        Thread thread3 = new Thread(runnable);</span><br><span class="line">        thread1.start();</span><br><span class="line">        thread2.start();</span><br><span class="line">        Thread.sleep(2000);</span><br><span class="line">        thread3.start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行结果：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">运行 Thread-0</span><br><span class="line">运行 Thread-1</span><br><span class="line">new Singleton()执行完毕</span><br><span class="line">Exception in thread &quot;Thread-0&quot; java.lang.NullPointerException</span><br><span class="line">	at com.aspire.mall.task.carwash.job.Singleton$1.run(Singleton.java:39)</span><br><span class="line">	at java.lang.Thread.run(Thread.java:748)</span><br><span class="line">运行 Thread-2</span><br><span class="line">Exception in thread &quot;Thread-2&quot; java.lang.NullPointerException</span><br><span class="line">	at com.aspire.mall.task.carwash.job.Singleton$1.run(Singleton.java:39)</span><br><span class="line">	at java.lang.Thread.run(Thread.java:748)</span><br><span class="line">get name :chenchenchen</span><br></pre></td></tr></table></figure>

<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210605203351.png"></p>
<p><img src="upload%5Cimage-20210605205532011.png" alt="image-20210605205532011"></p>
<p>我们可以想出两个办法来实现线程安全的延迟初始化。</p>
<ul>
<li>不允许2和3重排序（在JDK 1.5后可以基于volatile来解决）；</li>
<li>允许2和3重排序，但不允许其他线程“看到”这个重排序（可以使用静态内部类解决）；</li>
</ul>
<p>基于volatile的双重检查锁定的解决方案</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">private volatile static Instance instance;</span><br></pre></td></tr></table></figure>

<p>基于类初始化的解决方案</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">public class InstanceFactory &#123;</span><br><span class="line">    private static class InstanceHolder &#123;</span><br><span class="line">        public static Instance instance = new Instance();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public static Instance getInstance() &#123;</span><br><span class="line">    	//InstanceHolder类在这里会被初始化</span><br><span class="line">        return InstanceHolder.instance ;  </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h1 id="JMM多线程内存模型"><a href="#JMM多线程内存模型" class="headerlink" title="JMM多线程内存模型"></a>JMM多线程内存模型</h1><p>通俗来说，JMM是一套多线程读写共享数据时，对数据的可见性，有序性和原子性的规则。</p>
<p>JVM实现不同会造成“翻译”的效果不同，不同CPU平台的机器指令有千差万别，无法保证同一份代码并发下的效果一致。所以需要一套统一的规范来约束JVM的翻译过程，保证并发效果一致性。</p>
<p>Java多线程内存模型跟cpu缓存模型类似，是基于cpu缓存模型来建立的。Java线程内存模型是标准化的，屏蔽掉了底层不同计算机的区别。</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210531220319.png"></p>
<h2 id="JMM数据原子操作"><a href="#JMM数据原子操作" class="headerlink" title="JMM数据原子操作"></a>JMM数据原子操作</h2><ul>
<li>read(读取)︰从主内存读取数据</li>
<li>load(载入)︰将主内存读取到的数据写入工作内存.</li>
<li>use(使用)∶从工作内存读取数据来计算</li>
<li>assign(赋值)∶将计算好的值重新赋值到工作内存中. </li>
<li>store(存储)∶将工作内存数据写入主内存</li>
<li>write(写入):将store过去的变量值赋值给主内存中的变量.</li>
<li>lock(锁定)∶将主内存变量加锁，标识为线程独占状</li>
<li>unlock(解锁)︰将主内存变量解锁,解锁后其他线程可以锁定该变量</li>
</ul>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210531215945.png"></p>
<h2 id="happens-before规则"><a href="#happens-before规则" class="headerlink" title="happens-before规则"></a>happens-before规则</h2><p>即前一个操作的结果可以被后续的操作获取。</p>
<ul>
<li>程序的顺序性规则：在一个线程内一段代码的执行结果是有序的。虽然还会指令重排，但是随便它怎么排，结果是按照我们代码的顺序生成的不会变！</li>
<li>volatile规则： 就是如果一个线程先去写一个volatile变量，然后一个线程去读这个变量，那么这个写操作的结果一定对读的这个线程可见。</li>
<li>传递性规则：happens-before原则具有传递性，即A happens-before B ， B happens-before C，那么A happens-before C。</li>
<li>管程锁定规则：无论是在单线程环境还是多线程环境，对于同一个锁来说，一个线程对这个锁解锁之后，另一个线程获取了这个锁都能看到前一个线程的操作结果！(管程是一种通用的同步原语，synchronized就是管程的实现）</li>
<li>线程启动规则：在主线程A执行过程中，启动子线程B，那么线程A在启动子线程B之前对共享变量的修改结果对线程B可见。</li>
<li>线程终止规则： 在主线程A执行过程中，子线程B终止，那么线程B在终止之前对共享变量的修改结果在线程A中可见。</li>
<li>线程中断规则： 对线程interrupt()方法的调用先行发生于被中断线程代码检测到中断事件的发生，可以通过Thread.interrupted()检测到是否发生中断。</li>
<li>对象终结规则：一个对象的初始化的完成，也就是构造函数执行的结束一定 happens-before它的finalize()方法。</li>
</ul>
<h1 id="volatile关键字"><a href="#volatile关键字" class="headerlink" title="volatile关键字"></a>volatile关键字</h1><h2 id="什么是volatile"><a href="#什么是volatile" class="headerlink" title="什么是volatile"></a>什么是volatile</h2><p>volatile是一种同步机制，比synchronized或者Lock相关类更轻量级，因为使用volacile并不会发生上下文切换等开销很大的行为<br>volatile是无锁的，并且只能修饰单个属性</p>
<h2 id="什么时候适合用vilatile"><a href="#什么时候适合用vilatile" class="headerlink" title="什么时候适合用vilatile"></a>什么时候适合用vilatile</h2><p>一个共享变量始终只被各个线程赋值，没有其他操作<br>作为刷新的触发器，引用刷新之后使修改内容对其他线程可见（如CopyOnRightArrayList底层动态数组通过volatile修饰，保证修改完成后通过引用变化触发volatile刷新，使其他线程可见）</p>
<h2 id="volatile的作用"><a href="#volatile的作用" class="headerlink" title="volatile的作用"></a>volatile的作用</h2><p>可见性保障：修改一个volatile修饰变量之后，会立即将修改同步到主内存，使用一个volatile修饰的变量之前，会立即从主内存中刷新数据。保证读取的数据都是最新的，之前的修改都是可见的。</p>
<p>有序性保障（禁止指令重排序优化）：有volatile修饰的变量，赋值后多了一个“内存屏障“（ 指令重排序时不能把后面的指令重排序到内存屏障之前的位置）</p>
<h2 id="volatile的性能"><a href="#volatile的性能" class="headerlink" title="volatile的性能"></a>volatile的性能</h2><p>volatile 的读性能消耗与普通变量几乎相同，但是写操作稍慢，因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。</p>
<h2 id="volatile缓存可见性实现原理"><a href="#volatile缓存可见性实现原理" class="headerlink" title="volatile缓存可见性实现原理"></a>volatile缓存可见性实现原理</h2><p><strong>lock前缀指令 + MESI缓存一致性协议</strong></p>
<p>对volatile修饰的变量执行写操作时，JVM会发送一条lock前缀指令给CPU，CPU在执行写操作之后会立即将这个值写回主内存。</p>
<p>同时因为有MESI缓存一致性协议，各个CPU都会对总线进行嗅探，如果本地缓存中的数据被修改了，就会将自己本地缓存的数据过期掉。再次读取变量时，就会从主内存重新加载最新的数据了。</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210531215945.png"></p>
<p>如果发现别人修改了某个缓存的数据，那么CPU就会将自己本地缓存的数据过期掉，然后这个CPU上执行的线程在读取那个变量的时候，就会从主内存重新加载最新的数据了</p>
<p>IA-32和Intel 64架构软件开发者手册对lock指令的解释：</p>
<ul>
<li>会将当前处理器缓存行的数据立即写回到系统内存。</li>
<li>在其他CPU里缓存了该内存地址的数据变为无效(MESI协议)。</li>
<li>提供内存屏障功能，使lock前后指令不能重排序。</li>
</ul>
<h2 id="volatile有序性实现原理"><a href="#volatile有序性实现原理" class="headerlink" title="volatile有序性实现原理"></a>volatile有序性实现原理</h2><p><strong>内存屏障</strong></p>
<ul>
<li>每个volatile写操作前面，加StoreStore屏障，禁止上面的普通写和volatile写重排；</li>
<li>每个volatile写操作后面，加StoreLoad屏障，禁止跟下面的volatile读/写重排；</li>
<li>每个volatile读操作后面，加LoadLoad屏障，禁止跟下面的普通读和voaltile读重排；</li>
<li>每个volatile读操作后面，加LoadStore屏障，禁止跟下面的普通写和volatile读重排；</li>
</ul>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210605204637.png"></p>
<p>LoadLoad读读屏障：确保Load1数据的装载先于Load2后所有装载指令，Load1对应的代码和Load2对应的代码，是不能指令重排的。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Load1：int localVar = this.variable</span><br><span class="line">LoadLoad读读屏障</span><br><span class="line">Load2：int localVar = this.variable2</span><br></pre></td></tr></table></figure>

<p>StoreStore写写屏障：确保Store1的数据一定刷回主存，对其他cpu可见，先于Store2以及后续指令。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Store1：this.variable = 1</span><br><span class="line">StoreStore写写屏障</span><br><span class="line">Store2：this.variable2 = 2</span><br></pre></td></tr></table></figure>

<p>LoadStore读写屏障：确保Load1指令的数据装载，先于Store2以及后续指令。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Load1：int localVar = this.variable</span><br><span class="line">LoadStore读写屏障</span><br><span class="line">Store2：this.variable = 2</span><br></pre></td></tr></table></figure>

<p>StoreLoad写读屏障：确保Store1指令的数据一定刷回主存，对其他cpu可见，先于Load2以及后续指令的数据装载</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Store1：this.variable = 2</span><br><span class="line">StoreLoad写读屏障</span><br><span class="line">Load2：int localVar = this.variable</span><br></pre></td></tr></table></figure>

<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210605205344.png"></p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210605205331.png"></p>
<h2 id="Java程序汇编代码查看"><a href="#Java程序汇编代码查看" class="headerlink" title="Java程序汇编代码查看"></a>Java程序汇编代码查看</h2><p>下载查看运行代码的汇编指令，放到JDK所在的/jre/bin路径下。</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210531225037.png"></p>
<p>测试代码</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210531225505.png"></p>
<p>JVM启动参数添加上VolatileVisibilityTest.java的测试类名的执行方法prepareData：-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*VolatileVisibilityTest.prepareData</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210531225422.png"></p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210531225826.png"></p>
<p>参考：</p>
<p>JVM内存结构和Java内存模型别再傻傻分不清了：<a target="_blank" rel="noopener" href="https://blog.csdn.net/qq_41170102/article/details/104650162">https://blog.csdn.net/qq_41170102/article/details/104650162</a></p>
<p>双重检查锁定(DCL)与延迟初始化：<a target="_blank" rel="noopener" href="https://blog.csdn.net/u013190088/article/details/83154443">https://blog.csdn.net/u013190088/article/details/83154443</a></p>
<p>双重检查锁（DCL）问题：<a target="_blank" rel="noopener" href="https://blog.csdn.net/Dongguabai/article/details/82828125">https://blog.csdn.net/Dongguabai/article/details/82828125</a></p>
<p><a target="_blank" rel="noopener" href="https://processon.com/view/6061d2ee1e0853028ab68bd5?fromnew=1">https://processon.com/view/6061d2ee1e0853028ab68bd5?fromnew=1</a></p>
<p><a target="_blank" rel="noopener" href="https://processon.com/view/5fcb5f777d9c0837c09e0025?fromnew=1">https://processon.com/view/5fcb5f777d9c0837c09e0025?fromnew=1</a></p>
 
      <!-- reward -->
      
      <div id="reword-out">
        <div id="reward-btn">
          打赏
        </div>
      </div>
      
    </div>
    

    <!-- copyright -->
    
    <div class="declare">
      <ul class="post-copyright">
        <li>
          <i class="ri-copyright-line"></i>
          <strong>版权声明： </strong>
          
          本博客所有文章除特别声明外，著作权归作者所有。转载请注明出处！
          
        </li>
      </ul>
    </div>
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=http://example.com/2021/06/01/Java/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B(JMM)/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Java/" rel="tag">Java</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2021/06/02/Mysql/sql%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            Mysql和InnoDB架构对应的一次sql执行过程
          
        </div>
      </a>
    
    
      <a href="/2021/05/23/Java/Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%86%85%E5%AD%98%E7%BB%93%E6%9E%84(JVM)/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">Java虚拟机内存结构（JVM）</div>
      </a>
    
  </nav>

   
<!-- valine评论 -->
<div id="vcomments-box">
  <div id="vcomments"></div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/Valine.min.js"></script>
<script>
  new Valine({
    el: "#vcomments",
    app_id: "",
    app_key: "",
    path: window.location.pathname,
    avatar: "monsterid",
    placeholder: "给我的文章加点评论吧~",
    recordIP: true,
  });
  const infoEle = document.querySelector("#vcomments .info");
  if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
    infoEle.childNodes.forEach(function (item) {
      item.parentNode.removeChild(item);
    });
  }
</script>
<style>
  #vcomments-box {
    padding: 5px 30px;
  }

  @media screen and (max-width: 800px) {
    #vcomments-box {
      padding: 5px 0px;
    }
  }

  #vcomments-box #vcomments {
    background-color: #fff;
  }

  .v .vlist .vcard .vh {
    padding-right: 20px;
  }

  .v .vlist .vcard {
    padding-left: 10px;
  }
</style>

 
   
     
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2021
        <i class="ri-heart-fill heart_icon"></i> Chenyy
      </li>
    </ul>
    <ul>
      <li>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></s>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="ChenyyのBlog"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">文章</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/book">收藏</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/about/me">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->


<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: '.tocbot',
    contentSelector: '.article-entry',
    headingSelector: 'h1, h2, h3, h4, h5, h6',
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: 'main',
    positionFixedSelector: '.tocbot',
    positionFixedClass: 'is-position-fixed',
    fixedSidebarOffset: 'auto'
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


    
  </div>
</body>

</html>